package org.app.appjson.adapter;

import org.app.appjson.exception.AppJsonException;
import org.app.appjson.reader.JsonLexer;
import org.app.appjson.reader.JsonToken;
import org.app.appjson.reader.ReadHandler;
import org.app.appjson.reader.cache.JsonCache;
import org.app.appjson.writer.WriteHandler;

import java.lang.reflect.Field;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 反射适配器
 * @author lizhongsi
 * @version 1.0
 * @since 1.0
 */
public class ReflectiveAdapter implements TypeAdapter {

    public static Object read(ReadHandler handler) throws AppJsonException {
        JsonLexer lexer = handler.getLexer();
        if (lexer.peek().getType() == JsonToken.NULL) {
            lexer.next();
            return null;
        }
        if (lexer.peek().getType() != JsonToken.BEGIN_OBJECT) {
            throw new AppJsonException("ReflectAdapter exception.");
        }
        lexer.next(); //消费{
        Object instance = handler.newInstance(); //根据上下文创建一个实例
        // 先从cache中获取fieldMap
        Map<String, Field> fieldMap = JsonCache.getFieldCache(instance.getClass().getName());
        if (fieldMap == null) {
            fieldMap = getAllFields(instance.getClass());
            JsonCache.putFieldCache(instance.getClass().getName(), fieldMap);
        }
        while (lexer.peek().getType() != JsonToken.END_OBJECT) {
            if (lexer.peek().getType() != JsonToken.STRING) {
                throw new AppJsonException("The type of key must be String.");
            }
            String key = lexer.next().getValue();// json的key必须是String
            if (lexer.peek().getType() != JsonToken.SEP_COLON) {// key之后必须是':'
                throw new AppJsonException("The char after key must be ':'.");
            }
            lexer.next();// 消费':'
            Field field = fieldMap.get(key);
            if (field == null) { //不存在该key的field
                handler.doRead();//消费key对应的value
                if (lexer.peek().getType() == JsonToken.SEP_COMMA) {// 消费两个键值对之间的','
                    lexer.next();
                }
                continue;
            }
            field.setAccessible(true);//设置字段操作权限
            Class<?> classOfT = handler.getObjectClass(field.getType());//根据字段的类型获取
            Object value = handler.doRead(classOfT);
            try {
                field.set(instance, value);//为实例赋值
            } catch (IllegalAccessException e) {
                throw new AppJsonException("Set value to field exception: " + field.getName());
            }
            if (lexer.peek().getType() == JsonToken.SEP_COMMA) {// 消费两个键值对之间的','
                lexer.next();
            }
        }
        return instance;
    }

    /**
     * 获取本类及其所有超类（不包括Object类）的字段属性
     * @param clazz 当前类对象
     * @return 字段
     */
    public static Map<String, Field> getAllFields(Class<?> clazz) {
        Map<String, Field> result = new LinkedHashMap<>();
        while (clazz != Object.class) {
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                result.put(field.getName(), field);
            }
            clazz = clazz.getSuperclass();
        }
        return result;
    }

    public static void write(WriteHandler handler) throws AppJsonException {
        Object object = handler.getObject();
        handler.write("{");
        Map<String, Field> fieldMap = getAllFields(object.getClass());
        boolean isObjectEmpty = true;
        for (String fieldName : fieldMap.keySet()) {
            if (!isObjectEmpty) {//非空对象，拼一个逗号','
                handler.write(",");
            }
            handler.write("\"");
            handler.write(fieldName);
            handler.write("\"");
            handler.write(":");
            Field field = fieldMap.get(fieldName);
            field.setAccessible(true);// 设置字段可达性为true，private的字段变成可访问
            Object obj;
            try {
                obj = field.get(object); //获取字段对应的值
            } catch (IllegalAccessException e) {
                throw new AppJsonException("Can not get value of object."+ e);
            }
            handler.doWrite(obj); //递归写对象
            isObjectEmpty = false;
        }
        handler.write("}");
    }
}
